package cn.youfule.mecs.netty.protocol;

import cn.youfule.mecs.netty.MecsProtocol;
import cn.youfule.mecs.netty.ProtocolField;
import cn.youfule.mecs.netty.util.CRC16Utils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public abstract class AbstractEncoder<I extends AbstractProtocolContext> {


    public void encode(ChannelHandlerContext channelHandlerContext, MecsProtocol<I> prot, ByteBuf buffer) throws Exception {
        //写指针
        int index = buffer.writerIndex();
        buffer.writeShort(MecsProtocol.START);
        buffer.writeInt(prot.getId());
        buffer.writeByte(prot.getFactory());
        buffer.writeByte(prot.getType());
        buffer.writeByte(prot.getFunctionCode());
        if(prot.getContext() != null) {
            this.encodeContext(channelHandlerContext, prot.getContext(), buffer);
        }
        // compute the crc32 and write to out
        byte[] frame = new byte[buffer.readableBytes()];
        buffer.getBytes(index, frame);
        buffer.writeBytes(CRC16Utils.CRC16(frame));

        byte[] bytes = new byte[buffer.readableBytes()];
        for (int i = buffer.readerIndex();i < buffer.readableBytes();i++){
            bytes[i] = buffer.getByte(i);
        }
        log.debug("发送的数据={}" ,CRC16Utils.bytesToHexString(bytes));
    }

    /**
     * 是否支持该Protocol
     * @param prot
     * @return
     */
    public boolean isSupport(MecsProtocol prot){
        if(prot == null){
            log.error("主站下发或应答内容为空");
            throw new RuntimeException("主站下发或应答内容为空");
        }
        //获取功能码
        int functionCode = prot.getFunctionCode();
        for (byte fc: this.supportFunctionCode()){
            if(functionCode == fc){
                return true;
            }
        }
        return false;
    }

    /**
     * 支持的功能码
     * @return
     */
    protected abstract byte[] supportFunctionCode();

    /**
     * 编码协议数据内容部分
     * @param ctx
     * @param context
     * @param buffer
     */
    protected void encodeContext(ChannelHandlerContext ctx, I context, ByteBuf buffer){
        buffer.writeByte(context.getStartIndex());
        buffer.writeByte(context.getContextLength());
        if(context.getContextLength() <= 0){
            return;
        }
        // 获取类模板
        Class c = context.getClass();
        List<Field> fieldList = Arrays.stream(c.getDeclaredFields()).filter(f -> f.isAnnotationPresent(ProtocolField.class))
                .sorted(Comparator.comparing(com -> com.getAnnotation(ProtocolField.class).start()))
                .collect(Collectors.toList());
        //buffer中数据内容部分的索引
        int writeIndex = 0;
        for (Field f:fieldList ){
            ProtocolField anno = f.getAnnotation(ProtocolField.class);
            int length = anno.length();
            int start = anno.start();

            //如果字段开始索引小于内容开始索引，则跳过字节
            if(start < context.getStartIndex()){
                continue;
            }
            //如果字段开始索引大于最大索引，则结束
            if(start >= context.getStartIndex()+context.getContextLength()){
                break;
            }
            while (start > context.getStartIndex() + writeIndex){
                //一个字节一个字节的跳过
                buffer.writeByte(0);
                writeIndex++;
            }

            if(!f.isAccessible()){
                f.setAccessible(true);
            }
            Class type = f.getType();
            boolean noWrite = false;
            try {
                if (length == 1) {
                    if(     type == int.class || type == Integer.class ||
                            type == short.class || type == Short.class ||
                            type == byte.class || type == Byte.class
                    ){
                        buffer.writeByte( (Integer)f.get(context));
                    }else{
                        noWrite = true;
                    }
                }else if(length == 2){
                    if (type == LocalTime.class){
                        this.writeTime(buffer,(LocalTime)f.get(context));
                    }else if (type ==int.class || type == Integer.class ||
                            type == short.class || type == Short.class){
                        buffer.writeShort((Integer) f.get(context));
                    }else{
                        noWrite = true;
                    }
                }else if(length == 3){
                    if (type == LocalDate.class){
                        this.writeDate(buffer,(LocalDate) f.get(context));
                    }else{
                        noWrite = true;
                    }
                }else if(length == 4){
                    if (type ==int.class || type == Integer.class){
                        buffer.writeInt((Integer) f.get(context));
                    }else{
                        noWrite = true;
                    }
                }else if (length == 6){
                    if(type == LocalDateTime.class){
                        this.writeDateTime(buffer,(LocalDateTime) f.get(context));
                    }else{
                        noWrite = true;
                    }
                }

                //如果上面没有匹配，则写入byte[length]
                if(noWrite){
                    buffer.writeBytes(new byte[length]);
                }
                //索引内容索引增加
                writeIndex += length;
            }catch (IllegalAccessException e){
                log.error("协议编码异常",e);
                throw new RuntimeException("协议编码异常");
            }
        }
    }

    private ByteBuf writeByte(ByteBuf buf,int data){
        return buf.writeByte(data & 0xff);
    }
    private ByteBuf writeShort(ByteBuf buf,int data){
        return buf.writeByte((data >> 8) & 0xFF).writeByte(data & 0xFF );
    }
    private ByteBuf writeInt(ByteBuf buf,int data){
        return buf.writeByte((data >> 24) & 0xFF).writeByte((data >> 16) & 0xFF).writeByte((data >> 8) & 0xFF).writeByte(data & 0xFF);
    }
    private ByteBuf writeTime(ByteBuf buf,LocalTime data){
        if(data != null) {
            //writeByte(buf,data.getHour());
            //writeByte(buf,data.getMinute());
            buf.writeByte(data.getHour());
            buf.writeByte(data.getMinute());
        }else{
            buf.writeByte(0);
            buf.writeByte(0);
        }
        return buf;
    }
    private ByteBuf writeDate(ByteBuf buf,LocalDate data){
        if(data != null) {
            //writeByte(buf,data.getYear());
            //writeByte(buf,data.getMonthValue());
            //writeByte(buf,data.getDayOfMonth());
            buf.writeByte(data.getYear());
            buf.writeByte(data.getMonthValue());
            buf.writeByte(data.getDayOfMonth());
        }else{
            buf.writeByte(0);
            buf.writeByte(0);
            buf.writeByte(0);
        }
        return buf;
    }
    private ByteBuf writeDateTime(ByteBuf buf,LocalDateTime data){
        if(data != null) {
            //writeByte(buf,data.getYear());
            //writeByte(buf,data.getMonthValue());
            //writeByte(buf,data.getDayOfMonth());
            //writeByte(buf,data.getHour());
            //writeByte(buf,data.getMinute());
            //writeByte(buf,data.getSecond());
            buf.writeByte(data.getYear());
            buf.writeByte(data.getMonthValue());
            buf.writeByte(data.getDayOfMonth());
            buf.writeByte(data.getHour());
            buf.writeByte(data.getMinute());
            buf.writeByte(data.getSecond());
        }else{
            buf.writeByte(0);
            buf.writeByte(0);
            buf.writeByte(0);
            buf.writeByte(0);
            buf.writeByte(0);
            buf.writeByte(0);
        }
        return buf;
    }
}
